package com.wjk.kylin.id.server.kid.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.wjk.kylin.id.server.dto.IdInfoDTO;
import com.wjk.kylin.id.server.dto.QueryIdInDTO;
import com.wjk.kylin.id.server.transactional.TransactionalHandle;
import com.wjk.kylin.id.server.exception.BaseError;
import com.wjk.kylin.id.server.exception.BusinessException;
import com.wjk.kylin.id.server.kid.entity.IdInfoEntity;
import com.wjk.kylin.id.server.kid.mapper.IdInfoMapper;
import com.wjk.kylin.id.server.kid.service.IdInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wjk.kylin.id.server.kid.service.IdTokenService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * <p>
 * id信息表 服务实现类
 * </p>
 *
 * @author wangjinkui
 * @since 2022-08-19
 */
@Slf4j
@Service
public class IdInfoServiceImpl extends ServiceImpl<IdInfoMapper, IdInfoEntity> implements IdInfoService {

    @Autowired
    public IdTokenService idTokenService;
    @Autowired
    private TransactionalHandle transactionalHandle;

    @Override
    public IdInfoDTO queryIdInfo(QueryIdInDTO inDTO) {
        //1.查询token
        this.verifyTokenAndBizType(inDTO);
        //2.根据bizType查询数据
        IdInfoEntity idInfoEntity = this.getIdInfoEntity(inDTO);

        //3.更新step
//        setStep(idInfoEntity, inDTO);

        //调用执行事务
        final IdInfoEntity finalIdInfoEntity = idInfoEntity;
        //第一次
        boolean update = transactionalHandle.execute(() -> updateIdInfoEntity(finalIdInfoEntity));

        //验证 update ,进行再次更新，直到为true
        idInfoEntity = this.getIdInfoEntityAndUpdate(update, idInfoEntity.getId(), inDTO, idInfoEntity);

        //组织返回数据
        return IdInfoDTO.builder()
                .beginId(idInfoEntity.getMaxId())
                //更新后的最大值
                .maxId(idInfoEntity.getMaxId() + idInfoEntity.getStep())
                .delta(idInfoEntity.getDelta())
                .build();
    }

    /**
     * 取最大的step，整数倍
     *
     * @param idInfoEntity
     * @param inDTO
     */
//    private void setStep(IdInfoEntity idInfoEntity, QueryIdInDTO inDTO) {
//        if (inDTO.getBatchSize() != null && inDTO.getBatchSize() > 0 && inDTO.getBatchSize() > idInfoEntity.getStep()) {
//            //向上取整
//            //(x + y - 1) / y
//            //((x + y - 1) / y ) * y
//            idInfoEntity.setStep(((inDTO.getBatchSize() + idInfoEntity.getStep() - 1) / idInfoEntity.getStep()) * idInfoEntity.getStep());
//        }
//    }

    /**
     * 验证 update ,进行再次更新，直到为true
     *
     * @param update
     * @param id
     * @param inDTO
     * @param idInfoEntity
     */
    private IdInfoEntity getIdInfoEntityAndUpdate(boolean update, Long id, QueryIdInDTO inDTO, IdInfoEntity idInfoEntity) {
        int count = 0;
        while (!update) {
            idInfoEntity = super.getById(id);
            if (idInfoEntity == null) {
                log.error("kylin_id_info 数据为空,token:{},bizType:{},id:{}", inDTO.getToken(), inDTO.getBizType(), id);
                throw new BusinessException(BaseError.BIZ_TYPE_PARAM_ERR);
            }
            //更新 step
//            setStep(idInfoEntity, inDTO);

            final IdInfoEntity finalIdInfoEntity = idInfoEntity;
            update = transactionalHandle.execute(() -> updateIdInfoEntity(finalIdInfoEntity));

            log.warn("update kylin_id_info,token:{},bizType:{},id:{},count:{},update:{}", inDTO.getToken(), inDTO.getBizType(), id, ++count, update);
        }

        return idInfoEntity;
    }


    /**
     * Transactional标记保证query和update使用的是同一连接
     * 事务隔离级别应该为READ_COMMITTED,Spring默认是DEFAULT(取决于底层使用的数据库，mysql的默认隔离级别为REPEATABLE_READ)
     * <p>
     * 如果是REPEATABLE_READ，那么在本次事务中循环调用tinyIdInfoDAO.queryByBizType(bizType)获取的结果是没有变化的，也就是查询不到别的事务提交的内容
     * 所以多次调用tinyIdInfoDAO.updateMaxId也就不会成功
     *
     * @param bizType
     * @return
     */
//    @Override
//    @Transactional(isolation = Isolation.READ_COMMITTED)
//    public SegmentId getNextSegmentId(String bizType) {
//        // 获取nextTinyId的时候，有可能存在version冲突，需要重试
//        for (int i = 0; i < Constants.RETRY; i++) {
//            TinyIdInfo tinyIdInfo = tinyIdInfoDAO.queryByBizType(bizType);
//            if (tinyIdInfo == null) {
//                throw new TinyIdSysException("can not find biztype:" + bizType);
//            }
//            Long newMaxId = tinyIdInfo.getMaxId() + tinyIdInfo.getStep();
//            Long oldMaxId = tinyIdInfo.getMaxId();
//            int row = tinyIdInfoDAO.updateMaxId(tinyIdInfo.getId(), newMaxId, oldMaxId, tinyIdInfo.getVersion(),
//                    tinyIdInfo.getBizType());
//            if (row == 1) {
//                tinyIdInfo.setMaxId(newMaxId);
//                SegmentId segmentId = convert(tinyIdInfo);
//                logger.info("getNextSegmentId success tinyIdInfo:{} current:{}", tinyIdInfo, segmentId);
//                return segmentId;
//            } else {
//                logger.info("getNextSegmentId conflict tinyIdInfo:{}", tinyIdInfo);
//            }
//        }
//        throw new TinyIdSysException("get next segmentId conflict");
//    }

    /**
     * 执行事务方法
     *
     * @param idInfoEntity
     * @return
     */
    private boolean updateIdInfoEntity(IdInfoEntity idInfoEntity) {
        //更新的数据
        IdInfoEntity updateIdInfoEntity = new IdInfoEntity();
        updateIdInfoEntity.setMaxId(idInfoEntity.getMaxId() + idInfoEntity.getStep());
        //更新太频繁，也没必要更新时间
//        updateIdInfoEntity.setUpdateTime(LocalDateTime.now());
        updateIdInfoEntity.setVersion(idInfoEntity.getVersion() + 1);

        //根据id 和 version 更新数据
        LambdaUpdateWrapper<IdInfoEntity> infoUpdateWrapper = new LambdaUpdateWrapper<>();
        infoUpdateWrapper.eq(IdInfoEntity::getId, idInfoEntity.getId());
        infoUpdateWrapper.eq(IdInfoEntity::getVersion, idInfoEntity.getVersion());

        return this.update(updateIdInfoEntity, infoUpdateWrapper);
    }

    //滴滴 Tinyid
//    public int updateMaxId(Long id, Long newMaxId, Long oldMaxId, Long version, String bizType) {
//        String sql = "update tiny_id_info set max_id= ?," +
//                " update_time=now(), version=version+1" +
//                " where id=? and max_id=? and version=? and biz_type=?";
//        return jdbcTemplate.update(sql, newMaxId, id, oldMaxId, version, bizType);
//    }

    /**
     * 查询 info 表数据
     *
     * @param inDTO
     * @return
     */
    private IdInfoEntity getIdInfoEntity(QueryIdInDTO inDTO) {
        LambdaQueryWrapper<IdInfoEntity> infoQueryWrapper = new LambdaQueryWrapper<>();
        infoQueryWrapper.eq(StringUtils.isNotBlank(inDTO.getBizType()), IdInfoEntity::getBizType, inDTO.getBizType());

        List<IdInfoEntity> list = this.list(infoQueryWrapper);

        if (CollectionUtils.isEmpty(list)) {
            log.error("kylin_id_info数据为空,token:{},bizType:{}", inDTO.getToken(), inDTO.getBizType());
            throw new BusinessException(BaseError.BIZ_TYPE_PARAM_ERR);
        } else if (list.size() > 1) {
            log.error("kylin_id_info数据大于1,token:{},bizType:{},count:{}", inDTO.getToken(), inDTO.getBizType(), list.size());
            throw new BusinessException(BaseError.BIZ_TYPE_PARAM_ERR);
        }

        //3.更新数据，并返回数据
        return list.get(0);
    }

    /**
     * 验证 token 表 数据
     *
     * @param inDTO
     */
    private void verifyTokenAndBizType(QueryIdInDTO inDTO) {
        Long count = idTokenService.queryTokenInfo(inDTO);
        if (count == 0) {
            log.error("kylin_id_token数据为空,token:{},bizType:{}", inDTO.getToken(), inDTO.getBizType());
            throw new BusinessException(BaseError.TOKEN_BIZ_TYPE_PARAM_ERR);
        } else if (count > 1) {
            log.error("kylin_id_token数据大于1,token:{},bizType:{},count:{}", inDTO.getToken(), inDTO.getBizType(), count);
            throw new BusinessException(BaseError.TOKEN_BIZ_TYPE_PARAM_ERR);
        }
    }
}
